package core.util;

/* This class may be used to read a file delimited
 * by commas (CSV), tabs or other characters. 
 * A List of parsed String tokens is returned for each line read from the buffer.
 * */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

public class DelimitedFileReader {
	private BufferedReader br;

	private boolean hasNext = true;
	private char delimeter;
	private char quotechar;
	private int linesToSkip;

	private boolean finishedSkippingLines;

	/** The defaults to use if none supplied by the constructor. */
	public static final char DEFAULT_DELIMETER = ',';
	public static final char DEFAULT_QUOTE_CHARACTER = '"';
	public static final int DEFAULT_LINES_TO_SKIP = 0;

	public DelimitedFileReader(Reader reader) {
		this(reader, DEFAULT_DELIMETER);
	}

	public DelimitedFileReader(Reader reader, char delimeter) {
		this(reader, delimeter, DEFAULT_LINES_TO_SKIP);
	}

	public DelimitedFileReader(Reader reader, char delimeter, int lines) {
		this(reader, delimeter, lines, DEFAULT_QUOTE_CHARACTER);
	}

	public DelimitedFileReader(Reader reader, char delimeter,
			int lines_to_skip, char quotechar) {
		this.br = new BufferedReader(reader);
		this.delimeter = delimeter;
		this.linesToSkip = lines_to_skip;
		this.quotechar = quotechar;
		if (this.linesToSkip > 0) {
			finishedSkippingLines = false;
		} else {
			finishedSkippingLines = true;
		}
	}

	/**
	 * Reads the entire file into a List of String[]. Each String[] represents
	 * the tokens found on a read line.
	 */
	public List<String[]> readAll() throws IOException {
		List<String[]> allElements = new ArrayList<String[]>();
		while (hasNext) {
			String[] nextLineAsTokens = readNext();
			if (nextLineAsTokens != null)
				allElements.add(nextLineAsTokens);
		}
		return allElements;
	}

	/**
	 * Reads the next line from the buffer and converts to a string array.
	 */
	public String[] readNext() throws IOException {
		String nextLine = getNextLine();
		return hasNext ? parseLine(nextLine) : null;
	}

	/**
	 * Returns the next line from the file without the end newline.
	 */
	private String getNextLine() throws IOException {
		if (!finishedSkippingLines) {
			for (int l = 0; l < linesToSkip; l++) {
				br.readLine();
			}
			finishedSkippingLines = true;
		}

		String nextLine = br.readLine();
		if (nextLine == null) {
			hasNext = false;
		}
		return hasNext ? nextLine : null;
	}

	/**
	 * Parses a String and returns an array of tokens based on the delimiter.
	 */
	private String[] parseLine(String nextLine) throws IOException {
		if (nextLine == null) {
			return null;
		}

		List<String> lineTokens = new ArrayList<String>();
		StringBuffer sbuf = new StringBuffer();
		boolean foundQuote = false;
		do {
			if (foundQuote) { // continuing a quoted section that contained
				// embedded newline(s) - append the newline
				sbuf.append("\n");
				nextLine = getNextLine();
				if (nextLine == null)
					break;
			}
			for (int i = 0; i < nextLine.length(); i++) {
				char c = nextLine.charAt(i);
				if (c == quotechar) {
					// Check for quoted quotes (e.g., """)
					if (foundQuote && nextLine.length() > (i + 1) // Make sure
							// were not
							// at eol.
							&& nextLine.charAt(i + 1) == quotechar) // if next
					// char is a
					// quote too
					{
						// Two quotes in a row => one quote char.
						// Keep one, skip over the next.
						sbuf.append(nextLine.charAt(i + 1));
						i++;
					} else {
						foundQuote = !foundQuote;
						// Check for embedded quotes (e.g, x,a"b"c,y
						if (i > 2 // not at the beginning of the line
								&& nextLine.charAt(i - 1) != this.delimeter // not
								// at
								// the
								// start
								// of
								// an
								// escape
								// sequence
								&& nextLine.length() > (i + 1)
								&& nextLine.charAt(i + 1) != this.delimeter // not
						// at
						// the
						// end
						// of
						// an
						// escape
						// sequence
						) {
							sbuf.append(c);
						}
					}
				} else {
					if (c == delimeter && !foundQuote) {
						lineTokens.add(sbuf.toString());
						sbuf = new StringBuffer(); // start work on next token
					} else {
						sbuf.append(c);
					}
				}
			}
		} while (foundQuote);
		lineTokens.add(sbuf.toString());
		return lineTokens.toArray(new String[0]);
	}

	/* Close the reader */
	public void close() throws IOException {
		br.close();
	}

}